import { isPromise } from '../core/helper'

const hasOwn = Object.prototype.hasOwnProperty

/**
 * Form Mixin
 *
 * @param {String} ref    - form ref
 * @param {Object} model  - data model
 */
export default function (ref, model, {
  updateFieldsAfterCreated = true
} = {}
) {
  return {
    inheritAttrs: false,
    inject: {
      Modal: {
        default: () => {
          return null
        }
      }
    },
    props: {
      record: Object
    },

    data () {
      return {
        // 模型数据
        formModel: model
      }
    },

    methods: {
      /* Override
      ------------------------------------------------------------------------------------ */

      /**
       * 是否更新字段
       *
       * 默认当传入record
       * 可以被覆盖
       *
       * @returns {*|computed.visible}
       */
      shouldFieldsUpdate () {
        return !!this.record && (!this.Modal || this.Modal.visible)
      },

      /**
      *  初始化表单
      *
      * @param {Object} defaultValues  - 表单初始数据
      */
      initDefaults (defaultValues = {}) {
        if (!this.$isEmpty(defaultValues)) {
          this.$nextTick(() => {
            Object.keys(defaultValues).forEach((key) => {
              if (this.$isEmpty(this[ref][key])) {
                this[ref][key] = defaultValues[key]
              }
            })
          })
        }
        return Object.assign({}, model)
      },

      /**
       * 表单提交前处理数据
       *
       * @param {Object} data - 表单数据
       * @returns {Object}
       */
      beforeSubmit (data) {
        return data
      },

      /* Public Methods
      ------------------------------------------------------------------------------------ */

      /**
       * Form Reset
       */
      resetFields () {
        this.$refs[ref].resetFields()
      },

      /**
       * 校验提交表单
       *
       * formModel存在时则提取formModel字段
       * beforeSubmit返回false或reject会阻止提交
       * 默认以_开头不会被提交
       *
       * @param callback
       * @returns {*}
       */
      submit (callback) {
        const form = this.$refs[ref]
        const beforeSubmit = this.beforeSubmit
        return form.validate((valid, errors) => {
          const maybePromise = beforeSubmit(this.$cloneDeep(form.model))
          if (isPromise(maybePromise)) {
            maybePromise.then(data => callback(valid, this.$cloneDeep(
              model ? this.$only(data, Object.keys(model).filter(key => key.charAt(0) !== '_')) : data
            ), errors))
          } else if (maybePromise !== false) {
            callback(
              valid,
              this.$cloneDeep(
                model ? this.$only(maybePromise, Object.keys(model).filter(key => key.charAt(0) !== '_')) : maybePromise
              ),
              errors
            )
          }
        })
      },

      /**
       * 更新form表单字段
       *
       * @param {String} key - 上下文中formData的key默认取ruleForm
       * @param {Boolean} watch - 监听record变化更新form
       */
      updateFieldValues (key, watch) {
        key = key || ref || 'ruleForm'

        if (!this._formKey) {
          this._formKey = key
        }

        if (this.shouldFieldsUpdate()) {
          this[key] = {
            ...this[key],
            ...this.record
          }
        }

        // 对record进行监听
        if (watch && !this.isWatching) {
          this.$watch('record', () => this.updateFieldValues(key, false))
          this.isWatching = true
        }
      },

      /**
       * watch select change
       *
       * selectData 优先使用valueKey查找
       * @param {String}  valueKey    - value property
       * @param {String}  textKey     - label property
       * @param {Array}   selectData  - select data
       */
      watchSelect (valueKey, textKey, selectData, cb = this.$noop) {
        if (!valueKey || !textKey) return

        let formKey = this._formKey || ref

        if (hasOwn.call(this.formModel, valueKey)) {
          let path = `${formKey}.${valueKey}`
          this.$watch(path, (val, oldVal) => {
            let node = selectData.find(v => v[valueKey] === val || v['value'] === val)
            if (node) {
              this[formKey][textKey] = node[textKey] || node['label']
            }
            cb(val, oldVal)
          })
        }
      }
    },

    mounted () {
      if (updateFieldsAfterCreated) {
        this.updateFieldValues()
      }
    }
  }
}
